//Balexino V0.1
//Date June 27, 2013

// unofficial fork from "Vinduino" portable soil moisture sensor code V3.00
// Date December 31, 2012
// Reinier van der Lee and Theodore Kaskalis
// www.vanderleevineyard.com

// include the library code only for LCD display version
#include <Bounce.h>
#include <LiquidCrystal.h>
#include <math.h>
#include "LedBlink.h"
#include <LowPower.h>
#include "variables.h"


typedef struct  // Structure to be used in percentage and resistance values matrix to be filtered (have to be in pairs)
{        
  int moisture;
  long resistance;
} 
sensorRead;

int higherHumidityLimit;
int lowerHumidityLimit;


boolean buttonPressedOnStartMenu = false;
boolean startMenuExecutedOnce = false;
unsigned int lastButtonInputTime;

//utilisé pour calculer la durée d'attente
const int wdtSleepTime = 8; //ne doit pas être changé à  moins de changer l'instruction LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);.

#ifdef ENABLE_LCD

// define Ohm character
byte ohm[8] = 
{
  B00000,
  B01110,
  B10001,
  B10001,
  B10001,
  B01010,
  B11011,
  B00000
};

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);

#endif

LedBlink blinker = LedBlink(ledPin);

Bounce bouncer = Bounce(buttonPin, 10);

int activeDigitalPin = digitalPin1;         // 6 or 7 interchangeably
int supplyVoltageAnalogPin;                 // 6-ON: analogPin0, 7-ON: analogPin1
int sensorVoltageAnalogPin;                 // 6-ON: analogPin1, 7-ON: analogPin0

boolean firstReading = true;
sensorRead currentReading;
sensorRead readings[NUM_READS];             // Calculated moisture percentages and resistances to be sorted and filtered

boolean wateringEnabled;

int i;                                      // Simple index variable

#ifdef EMULATE_READINGS
//for debug mode : array of humidity values to emulate the sensor, activated if EMULATE_READINGS is defined.
const int numEmulatedValues = 2;
int currentEmulatedIndex = 0;
int emulatedMoistureValues[numEmulatedValues] = {
  44,
  56
};
#endif

int waitTime;

void setup() 
{
#ifdef DEBUG
  // initialize serial communications at 9600 bps:
  Serial.begin(9600); 
#endif

#ifdef ENABLE_LCD
  // set up the LCD's number of columns and rows: 
  lcd.createChar(1, ohm);
  lcd.begin(16, 2);
  lcd.print ("Vinduino Balex 3");
#endif

  // initialize the digital pin as an output.
  // Pin 6 is sense resistor voltage supply 1
  pinMode(digitalPin1, OUTPUT);    

  // initialize the digital pin as an output.
  // Pin 7 is sense resistor voltage supply 2
  pinMode(digitalPin2, OUTPUT);
  
#ifdef ENABLE_WATERING  
  pinMode(enableWateringPin, OUTPUT);  // initialise output for enabling latching relay
  pinMode(disableWateringPin,OUTPUT);  // initialise output for enabling latching relay
#endif

  waitTime = calculateWaitTime();
  
#ifdef DEBUG
    writeDebug("Welcome to Balexino ! we will have a great time you and me. (I hope) (no, in fact I have no hope, I'm a robot)");
    writeDebug("Setup function completed.");
#endif
}

void loop() {
  
  if(firstReading)
  {
    startupMenu();
  }
  else
  {
    takeReading();
    for(int i = 0; i < waitTime; i = i ++)
    {

#ifdef ENABLE_LOW_POWER
     
  #ifdef DEBUG
      writeDebug("mainLoop heartbeat : time remaining = " + String((waitTime * 8) - (i * wdtSleepTime))+" seconds"); 
  #endif

      LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
#else
      delay(1000);
#endif

    }
  }
}

void startupMenu()
{
  boolean buttonPressedChanged = bouncer.update();
  boolean buttonPressed = bouncer.read();
  
  if(!startMenuExecutedOnce)
  {
    blinker.startBlink(startupMenuBlinkInterval, startupMenuActiveTime);
    startMenuExecutedOnce = true;
    
#ifdef DEBUG
      writeDebug("Entering startup menu");
#endif
  }

  if(!buttonPressedOnStartMenu && buttonPressed)
  {
    buttonPressedOnStartMenu = true;
    medianHumidityPercentageTreshold = 0;
#ifdef DEBUG
    writeDebug("Button pressed for the first time, entering treshold value entry mode, percentage treshold is "
      + String(medianHumidityPercentageTreshold));
#endif
                
    blinker.stopBlink();
  }

  if(!buttonPressedOnStartMenu)
  {
    if(!blinker.update())
    {
    #ifdef DEBUG
      writeDebug("The blinker has timed out, exiting startup menu sequence.");
    #endif
      //the blinker has finished blinking - the startup menu timeout has been reached
      takeReading();
    }
  }
  else
  {
    if(buttonPressedChanged && buttonPressed)
    {
#ifdef DEBUG
      writeDebug("Georgio the button has been pressed !");
#endif
      if(medianHumidityPercentageTreshold >= 100)
      {
#ifdef DEBUG
        writeDebug("Woooh ! valeur maximale du treshold de 100 atteinte !!");
#endif
      }
      else
      {
        medianHumidityPercentageTreshold += 10;
#ifdef DEBUG
        writeDebug("increasing the treshold percentage by 10 (it is now " + String(medianHumidityPercentageTreshold) + ")");
#endif
      }
    }

    unsigned int currentMillis = millis();
    if(buttonPressedChanged && !buttonPressed)
    {
      lastButtonInputTime = currentMillis;
    }

    digitalWrite(ledPin, buttonPressed);

    if(currentMillis - lastButtonInputTime > 6000)
    {
#ifdef DEBUG
      writeDebug("6 seconds has passed, I will now repeat the value entered by rocking my led. (Value = " + String(medianHumidityPercentageTreshold) + ")");
#endif
      //no user input for at least 6 seconds - we will asume the user has finished setting the humidity limit.
      //we will blink a number of times equal to the humidity value that was set (divised by 10) to confirm the choice We will do it 3 times.
      for(int i = 0; i < 3; i++)
      {
        for(int j = 0; j < medianHumidityPercentageTreshold / 10; j++)
        {
          digitalWrite(ledPin, HIGH);
          delay(400);
          digitalWrite(ledPin, LOW);
          delay(400);
        }
        delay(2000);
      }
#ifdef DEBUG
        writeDebug("Value repetition completed, start menu sequence completed.  will now take a reading.");
#endif
      takeReading();
    }
  }
}

void takeReading()
{
#ifdef EMULATE_READINGS
  getCurrentEmulatedValue();
#else
  readValuesFromSensor();
#endif
  handleReading();
}

int calculateWaitTime()
{
  double integralPart;
  modf(secondsWaitTime / wdtSleepTime, &integralPart);
  int waitTime =int(integralPart); 
  #ifdef DEBUG
    writeDebug("calculated wait time : " + String(waitTime * wdtSleepTime));
  #endif
  return waitTime;
}

#ifdef EMULATE_READINGS
//returns an emulated moisture reading 
void getCurrentEmulatedValue()
{

  currentReading.moisture = emulatedMoistureValues[currentEmulatedIndex];
  writeDebug("getCurrentEmulatedValue() : will return moisture of " + String(currentReading.moisture));
  currentReading.resistance = 0; //do we need to emulate a resistance ?
  if(currentEmulatedIndex + 1 < numEmulatedValues)
    currentEmulatedIndex++;
  else
    currentEmulatedIndex = 0;
}
#endif

void readValuesFromSensor()
{
  // read sensor, filter, and calculate resistance value
  // Noise filter: median filter

  for (i=0; i<NUM_READS; i++) {

    setupCurrentPath();      // Prepare the digital and analog pin values

    // Read 1 pair of voltage values
    digitalWrite(activeDigitalPin, HIGH);                 // set the voltage supply on
    delay(10);
    int supplyVoltage = analogRead(supplyVoltageAnalogPin);   // read the supply voltage
    int sensorVoltage = analogRead(sensorVoltageAnalogPin);   // read the sensor voltage
    digitalWrite(activeDigitalPin, LOW);                  // set the voltage supply off  
    delay(10); 

    // Calculate resistance and moisture percentage without overshooting 100
    // the 0.5 add-term is used to round to the nearest integer
    // Tip: no need to transform 0-1023 voltage value to 0-5 range, due to following fraction
    readings[i].resistance = long( float(knownResistor) * ( supplyVoltage - sensorVoltage ) / sensorVoltage + 0.5 );
    readings[i].moisture = min( int( pow( readings[i].resistance/31.65 , 1.0/-1.695 ) * 400 + 0.5 ) , 100 );

  }
  // end of multiple read loop

  // Sort the moisture-resistance vector according to moisture
  sortMoistures();

  currentReading = readings[NUM_READS/2];
}

void setupCurrentPath() {
  if(activeDigitalPin == digitalPin1) {
    activeDigitalPin = digitalPin2;
    supplyVoltageAnalogPin = analogPin1;
    sensorVoltageAnalogPin = analogPin0;
  }
  else {
    activeDigitalPin = digitalPin1;
    supplyVoltageAnalogPin = analogPin0;
    sensorVoltageAnalogPin = analogPin1;
  }
}

// Selection sort algorithm
void sortMoistures() {
  int j;
  sensorRead temp;
  for(i=0; i<NUM_READS-1; i++)
    for(j=i+1; j<NUM_READS; j++)
      if(readings[i].moisture > readings[j].moisture) {
        temp = readings[i];
        readings[i] = readings[j];
        readings[j] = temp;
      }
}

void handleReading()
{
  outputReading();
  updateWateringTrigger();
}

void outputReading()
{

#ifdef DEBUG
  writeDebug("Reading from sensor : Moisture = " + String(currentReading.moisture) 
    + " Resistance = " + String(currentReading.resistance) + ", wateringEnabled = " + String(wateringEnabled));
#endif

#ifdef ENABLE_LCD
    writeReadingToLcd();
#endif

}

#ifdef ENABLE_LCD
void writeReadingToLcd()
{
  //set the cursor to column 0, line 0
  lcd.setCursor(0, 0);
  lcd.clear();
  lcd.print("Moisture: ");
  lcd.print(currentReading.moisture);
  lcd.print(" %");

  //set the cursor to column 0, line 1
  lcd.setCursor(0, 1);
  lcd.print("Sensor: " );
  lcd.print(currentReading.resistance);
  lcd.print(" ");
  lcd.write(1);
}
#endif

void updateWateringTrigger()
{

  int moistureToUse = currentReading.moisture;
  if(firstReading)
  {
#ifdef DEBUG
    writeDebug("updateWateringTrigger : firstReading == true, we will trigger a refresh.");
#endif
    higherHumidityLimit = medianHumidityPercentageTreshold + humidityPercentageAmplitude;
    lowerHumidityLimit = medianHumidityPercentageTreshold - humidityPercentageAmplitude;
    //Initialize the wateringEnabled and moisture parameters to the desired value.
    //It will cause the watering to be enabled only if the moisture has reached the inferior limit, otherwise it will disable it.
    if(currentReading.moisture <= lowerHumidityLimit)
    {
      wateringEnabled = false; //the real reading is OK
    }
    else
    {
      //initialize the moisture to use to a little bit higher value than the higher limit, juste enough to trigger the desactivation.
      moistureToUse = higherHumidityLimit + 1;
      wateringEnabled = true;
    }

    bool wateringNeeded = (currentReading.moisture <= lowerHumidityLimit);
    setWateringEnabledState(wateringNeeded);
    firstReading = false;
  }
  else
  {
    if(moistureToUse > lowerHumidityLimit && moistureToUse < higherHumidityLimit) //inside normal range, no action needed.
    {
#ifdef DEBUG
      writeDebug("moisture of " + String(moistureToUse) + " is inside the normal range (" + String(lowerHumidityLimit) + " - " + String(higherHumidityLimit) + ").  No action needed.");
#endif
      return;
    }

    if(wateringEnabled && (moistureToUse >= higherHumidityLimit)) //moisture reached the upper limit, time to disable watering !
    {
#ifdef DEBUG
      writeDebug("moisture of " + String(moistureToUse) + " has reached the desactivation treshold (" + String(higherHumidityLimit) + ").");
#endif
      setWateringEnabledState(false);
    }
    else if(!wateringEnabled && (moistureToUse < lowerHumidityLimit)) //moisture reached the lower limit, time to water those vegetables and fruits !
    {
#ifdef DEBUG
      writeDebug("moisture of " + String(moistureToUse) + " has reached the activation treshold (" + String(lowerHumidityLimit) + ").");
#endif
      setWateringEnabledState(true);
    }
  }  
}

void setWateringEnabledState(boolean enable)
{
#ifdef ENABLE_WATERING
#ifdef DEBUG
  writeDebug("setWateringEnabledState : " + String(enable));
#endif

  if(enable)
  {
    digitalWrite(enableWateringPin, HIGH);    
    delay(relayOnTime);
    digitalWrite(enableWateringPin, LOW);
  }
  else
  {
    digitalWrite(disableWateringPin, HIGH);
    delay(relayOnTime);
    digitalWrite(disableWateringPin, LOW);
  }

  wateringEnabled = enable;
#endif
}

void writeDebug(String message)
{
  Serial.println(message);
  Serial.flush();
}

